#include <stdio.h>
#include <dlfcn.h>  // 动态加载动态库的头文件：dlopen()、dlsym()
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>

#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>

struct fb_var_screeninfo varinfo;//可变属性

//BMP格式头规范
struct bitmap_header
{
    int16_t type;
    int32_t size;//图像文件大小
    int16_t reserved1;
    int16_t reserved2;
    int32_t offbits;//bmp图像数据偏移量
}__attribute__((packed));

struct bitmap_info
{
    int32_t size;//本结构大小
    int32_t width;//图像宽
    int32_t height;//图像高
    int16_t planes;

    int16_t bit_count;//色深
    int32_t compression;
    int32_t size_img;//bmp数据大小，必须是4的整数倍
    int32_t X_pel;
    int32_t Y_pel;
    int32_t clrused;
    int32_t clrImportant;
}__attribute__((packed));

//以下结构体不一定存在于BMP文件中，除非bitmap_info.compression为真
struct rgb_quad
{
    int8_t blue;
    int8_t green;
    int8_t red;
    int8_t reserved;
}__attribute__((packed));


/*<==========LCD结构体==========>*/
typedef struct LCD
{
    int fd;            //文件描述符
    int width;         //宽
    int height;        //高
    int bpp;           //色深
    int row_size;      //一行多少字节
    int size;          //可见区域总字节
    char *fbmem;       //映射内存(显存)
}LCD;

/*<==========bmp结构体=============>*/
typedef struct BMP
{
    int fd;    //bmp图片的文件描述符
    int width; //图像宽
    int height;//图像高
    int bpp;   //图像色深
    int size;  //图像大小
    char *rgb; //rgb指针存储颜色信息
    int pad;//需要填充的空白字节
}BMP;


/*------获取屏幕可变信息------*/
void get_varinfo(int lcd_fd)
{
    if(ioctl(lcd_fd,FBIOGET_VSCREENINFO,&varinfo) != 0)
    {
        perror("获取LCD设备可变属性信息失败");
        return ;
    }
}

/*------初始化液晶屏------*/
void lcd_init(LCD *lcd)
{
    //打开液晶屏
    lcd->fd = open("/dev/fb0",O_RDWR);
    if(lcd->fd == -1)
    {
        perror("open /dev/fb0 fail");
        exit(1);//非零值:失败 
    }
    //获取屏幕可变信息
    get_varinfo(lcd->fd);
    lcd->width = varinfo.xres;
    lcd->height = varinfo.yres;
    lcd->bpp = varinfo.bits_per_pixel;//色深:一个像素占比特bits 3字节:24位图  lcd->bpp/8 = 一个像素占多少字节

    lcd->row_size = lcd->width * lcd->bpp /8; // 位宽 * 色深 /8
    lcd->size = lcd->row_size * lcd->height;

    //申请一块可见区大小的映射内存
    lcd->fbmem = mmap(NULL,lcd->size,PROT_WRITE | PROT_READ,MAP_SHARED,lcd->fd,0);

    if(lcd->fbmem == MAP_FAILED)
    {
        perror("mmap LCD fail");//映射内存失败
        close(lcd->fd);
        exit(1);
    }
    // printf("LCD的信息:\n");
    // printf("宽:%d\n",lcd->width);
    // printf("高:%d\n",lcd->height);
    // printf("色深:%d\n",lcd->bpp);
    // printf("行占:%d\n",lcd->row_size);
    // printf("总大小:%d\n",lcd->size);
}

/*--------初始化BMP图片信息----------*/
void bmp_info(const char *bmpname,BMP *bmp)
{
    //printf("bmpname:%s\n",bmpname);
    //打开BMP
    bmp->fd = open(bmpname,O_RDONLY);//可读
    if(bmp->fd == -1)
    {
        perror("open bmp fail");
        return ;
    }

    //第一、二个结构体是必有信息
    struct bitmap_header header;
    struct bitmap_info info;
   //读取BMP格式头，获取图片信息
    read(bmp->fd,&header,sizeof(struct bitmap_header));
    read(bmp->fd,&info,sizeof(struct bitmap_info));

    bmp->width = info.width;
    bmp->height = info.height;
    bmp->bpp = info.bit_count;
    bmp->size = info.size_img;
    bmp->rgb = calloc(1,bmp->size);
    if(bmp->rgb == NULL)
    {
        perror("rgb 缓冲区申请失败\n");
        return ; 
    }
    read(bmp->fd,bmp->rgb,bmp->size);
    //无效字节数
    bmp->pad = ((4-(bmp->width*bmp->bpp/8)%4))%4;

    // printf("bmp->width:%d\n",bmp->width);
    // printf("bmp->height:%d\n",bmp->height);
    // printf("bmp->bpp:%d\n",bmp->bpp);
    // printf("bmp->size:%d\n",bmp->size);
}

/*------显示图片---------*/
void show_bmp(LCD *lcd,BMP *bmp)
{
    //让rgb指向最末行的首字节
    bmp->rgb +=(bmp->width*bmp->bpp/8 + bmp->pad)*(bmp->height-1);

    int x,y;
    for(y = 0;y<lcd->height && y< bmp->height;y++)
    {
        for(x=0;x<lcd->width && x<bmp->width;x++)
        {
            //计算偏移 6818开发板是rgb888 32位图所以乘4
            int lcd_offset = (lcd->width*y+x)*4; 
            memcpy(lcd->fbmem+lcd_offset,bmp->rgb,bmp->bpp/8);
            bmp->rgb += (bmp->bpp/8);
        }
        //显示完一行，跳过pad个无效字节
        bmp->rgb += bmp->pad;
        //让rgb指向上一行行首
        bmp->rgb -=(bmp->width*bmp->bpp/8)*2;
    }
}

/*--------反初始化----------*/
void uninit(LCD *lcd,BMP *bmp)
{
    close(lcd->fd);
    close(bmp->fd);
}


int main(int argc, char const *argv[])
{
    char bmp_arr[][10]={"c4.bmp","c1.bmp","c2.bmp","c3.bmp"};
    // if(argc < 2)
    // {
    //     printf("没有添加BMP图片名\n");
    //     return -1;
    // }
    LCD lcd;
    BMP bmp;
    lcd_init(&lcd);
    int i=1;
    while(1)
    {
        bmp_info(bmp_arr[i-1],&bmp);
        i = i%3+1;
        show_bmp(&lcd,&bmp);
    }

    //bmp_info(argv[1],&bmp);
    //show_bmp(&lcd,&bmp);
    unint(&lcd,&bmp);
    return 0;
}
